class App_20201026215429 { constructor( canvasId ) { this.cc = document.getElementById( canvasId ).getContext( "2d" ); let scale = 2; let horizon = 0.9; let transX = this.cc.canvas.width / scale / 2; let transY = -this.cc.canvas.height * horizon / scale; this.cc.scale( scale, -scale ); this.cc.translate( transX, transY ); this.cc.circle = function( x, y, r, fillStyle, strokeStyle ) { this.beginPath(); this.arc( x, y, r, 0, 6.28, false ); this.closePath(); if( fillStyle ) { this.fillStyle = fillStyle; this.fill(); } if( strokeStyle ) { this.strokeStyle = strokeStyle; this.stroke(); } }.bind( this.cc ); this.cc.line = function( sx, sy, ex, ey, color ) { this.beginPath(); this.moveTo( sx, sy ); this.lineTo( ex, ey ); if( color ) this.strokeStyle = color; this.stroke(); } this.clearRect = function() { this.cc.clearRect( -this.cc.canvas.width / scale / 2, this.cc.canvas.height * horizon / scale, this.cc.canvas.width / scale, - this.cc.canvas.height / scale ); } let div = document.createElement( "div" ); this.cc.canvas.after( div ); let html = ( function() { /*
posing: test
*/ } ).toString().match( /\/\*([\s\S]*)\*\// )[ 1 ]; let rangeId = Date.now(); this.rangeId = rangeId; let textId = rangeId + 1; html = html.replace( /range1/, rangeId ); html = html.replace( /sliderselected/, textId ); div.outerHTML = html; document.getElementById( rangeId ).oninput = function( e ) { //押下時 this.testpose( e.target.value ); let name = Object.keys( this.poses )[ e.target.value ]; document.getElementById( textId ).innerHTML = name; }.bind( this ); this.svcs = new Array(); this.drawings = new Array(); let world = new SVC_Part_20201026215429( "world" ); world.addJoint( "世界>腹", 0, 80 ); world.addJoint( "世界>キャラ1", 0, 0 ); world.imgd.visibility = true; world.imgd.type = "horizon"; world.debug1 = true; let hara = new SVC_Part_20201026215429( "腹" ); hara.imgd.w = 18; hara.imgd.h = 15; hara.imgd.x = -12.5; hara.imgd.y = -15; hara.addJoint( "腹>胸", 0, 0 ); hara.addJoint( "腹>腰", 0, -12 ); let mune = new SVC_Part_20201026215429( "胸" ); mune.imgd.w = 20; mune.imgd.h = 20; mune.imgd.x = -12.5; mune.imgd.y = -5; mune.addJoint( "胸>首", 1, 14 ); mune.addJoint( "胸>左上腕", 1, 14 ); let jouwanL = new SVC_Part_20201026215429( "左上腕" ); jouwanL.imgd.w = 10; jouwanL.imgd.h = 23; jouwanL.imgd.x = -5; jouwanL.imgd.y = -23; jouwanL.addJoint( "左上腕>左前腕", 2, -20 ); let zenwanL = new SVC_Part_20201026215429( "左前腕" ); zenwanL.imgd.w = 10; zenwanL.imgd.h = 23; zenwanL.imgd.x = -7; zenwanL.imgd.y = -21; zenwanL.addJoint( "左前腕>左手", -2, -20 ); zenwanL.debug1 = true; let teL = new SVC_Part_20201026215429( "左手" ); teL.imgd.w = 10; teL.imgd.h = 10; teL.imgd.x = -5; teL.imgd.y = -10; teL.addJoint( "つかみ", 0, -5 ); teL.debug1 = true; let kubi = new SVC_Part_20201026215429( "首" ); kubi.imgd.w = 10; kubi.imgd.h = 10; kubi.imgd.x = -6; kubi.imgd.y = -2; kubi.addJoint( "首>頭", 0, 6 ); let atama = new SVC_Part_20201026215429( "頭" ); atama.imgd.w = 20; atama.imgd.h = 20; atama.imgd.x = -13; atama.imgd.y = -1; let kosi = new SVC_Part_20201026215429( "腰" ); kosi.imgd.w = 20; kosi.imgd.h = 15; kosi.imgd.x = -12.5; kosi.imgd.y = -13; kosi.addJoint( "腰>左もも", -2.5, -10 ); kosi.addJoint( "腰>右もも", -2.5, -10 ); let momoL = new SVC_Part_20201026215429( "左もも" ); momoL.imgd.w = 18; momoL.imgd.h = 30; momoL.imgd.x = -10; momoL.imgd.y = -28; momoL.addJoint( "左もも>左すね", -2.5, -27.5 ); let suneL = new SVC_Part_20201026215429( "左すね" ); suneL.imgd.w = 18; suneL.imgd.h = 30; suneL.imgd.x = -6.5; suneL.imgd.y = -28; suneL.addJoint( "左すね>左かかと", 2.5, -25 ); suneL.debug1 = true; let kakatoL = new SVC_Part_20201026215429( "左かかと" ); kakatoL.imgd.w = 20; kakatoL.imgd.h = 10; kakatoL.imgd.x = -12; kakatoL.imgd.y = -6; kakatoL.addJoint( "左かかと>左つま先", -10, -2 ); kakatoL.addJoint( "接地", -10, -5 ); kakatoL.debug1 = true; let tumasakiL = new SVC_Part_20201026215429( "左つま先" ); tumasakiL.imgd.w = 10; tumasakiL.imgd.h = 7; tumasakiL.imgd.x = -10; tumasakiL.imgd.y = -4; tumasakiL.addJoint( "接地", -10, 0 ); tumasakiL.debug1 = true; //build. world.connect( "世界>腹", hara ); hara.connect( "腹>胸", mune ); mune.connect( "胸>首", kubi ); mune.connect( "胸>左上腕", jouwanL ); jouwanL.connect( "左上腕>左前腕", zenwanL ); zenwanL.connect( "左前腕>左手", teL ); kubi.connect( "首>頭", atama ); hara.connect( "腹>腰", kosi ); kosi.connect( "腰>左もも", momoL ); momoL.connect( "左もも>左すね", suneL ); suneL.connect( "左すね>左かかと", kakatoL ); kakatoL.connect( "左かかと>左つま先", tumasakiL ); let tick = 3.14 / 10; this.poses = { chokuritu : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); }, kagami : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); atama.rotation = tick * -.5; kubi.rotation = tick * 0; mune.rotation = tick * .5; kosi.rotation = tick * -1; momoL.rotation = tick * -1.5; suneL.rotation = tick * 2.5; kakatoL.rotation = tick * -1.5; tumasakiL.rotation = tick * 0; jouwanL.rotation = tick * -4; zenwanL.rotation = tick * -2; teL.rotation = tick * -1; }, senobi : function() { hara.adjust( tumasakiL, "接地", world, "世界>キャラ1" ); this.reset(); atama.rotation = tick * -1.5; kubi.rotation = tick * -1; mune.rotation = tick * -0.5; kosi.rotation = tick * 0.5; momoL.rotation = tick * 0.5; suneL.rotation = tick * 0; kakatoL.rotation = tick * 2.5; tumasakiL.rotation = tick * -3; jouwanL.rotation = tick * -12; zenwanL.rotation = tick * 0; teL.rotation = tick * -1; }, bridge : function() { if( ! world.jointHash.tmp2 ) { this.kiseki.x = hara.abs.x; this.kiseki.y = hara.abs.y; let cursor = this.kiseki.indexG( 20 ); world.addJoint( "tmp2", cursor.x, cursor.y ); } hara.adjust( hara, "腹>腰", world, "tmp2" ); this.reset(); hara.testR = -3.14 / 2; atama.rotation = tick * -1; kubi.rotation = tick * -2; mune.rotation = -3.14 /10; kosi.rotation = 3.14 /10; momoL.rotation = 3.14 /6; suneL.rotation = 3.14 /4; kakatoL.rotation = 3.14 /4; tumasakiL.rotation = -3.14 /4; jouwanL.rotation = tick * -13; zenwanL.rotation = tick * 0; teL.rotation = tick * 0; }, sakadachi : function() { // hara.adjustByThisStop( teL, "つかみ", world ); // this.kiseki.x = hara.abs.x; // this.kiseki.y = hara.abs.y; let cursor = this.kiseki.indexG( 55 ); world.addJoint( "tmp3", cursor.x, cursor.y ); hara.adjust( teL, "つかみ", world, "tmp3" ); this.reset(); this.reset(); atama.rotation = tick * -1; kubi.rotation = tick * -2; mune.rotation = tick * -0.5; kosi.rotation = tick * 0.5; momoL.rotation = tick * 0; suneL.rotation = tick * 0; kakatoL.rotation = 3.14 /4; tumasakiL.rotation = tick * 1; jouwanL.rotation = tick * -8.85; zenwanL.rotation = tick * 0; teL.rotation = tick * -0.5; }, }; //debug. テスト用スライダの最大値をこのposesの最大値にする let s = document.getElementById( this.rangeId ); s.setAttribute( "max", Object.keys( this.poses ).length - 1 ); this.svcs.push( world ); this.drawings.push( world ); this.kiseki = new Kiseki_20201026215429( { f : function( x ) { return -x * x; }, x : -50, y : 100, zoomX : 1.6, zoomY : 0.2, zoom : 2, sx : -10, ex : 17.5, step : 0.5, cursor : 20, } ); this.drawings.push( this.kiseki ); this.testpose( s.value ); } start() { if( 1 ) { this.draw( this.cc ); } if( 0 ) { this.timerId = setInterval( function() { this.draw( this.cc ); this.flg = ! this.flg; }.bind( this ), 500 ); } } stop() { clearInterval( this.timerId ); } draw( cc ) { this.clearRect(); for( let i = 0; i < this.drawings.length; i++ ) { let drawing = this.drawings[ i ]; drawing.draw( cc ); } } //各SVC_Part_20201026215429のreset()を実行(主にrotationを0にする等) reset() { for( let i = 0; i < this.svcs.length; i++ ) { let svc = this.svcs[ i ]; svc.reset(); } } calc() { for( let i = 0; i < this.svcs.length; i++ ) { let svc = this.svcs[ i ]; svc.calc(); } } testpose( value ) { let name = Object.keys( this.poses )[ value ]; this.poses[ name ].call( this ); this.calc(); console.log( name ); this.draw( this.cc ); } }//App_20201026215429 class SVC_Part_20201026215429 { constructor( title ) { this.title = title; this.jointHash = new Object(); this.rotation = 0; this.adjustFrom = null; this.adjustFromJointName = null; this.adjustTo = null; this.adjustToJointName = null; this.abs = new Object(); this.parent = null; this.parentJoint = null; this.children = new Array(); this.imgd = new TestRect_20201026215429( "rect" ); this.debug1 = false; this.testR = 0; } addJoint( name, x, y ) { let joint = new Object(); joint.name = name; joint.x = x; joint.y = y; this.jointHash[ name ] = joint; } //---connect //他のSVC_Part_20201026215429を自身のJointへ接続 connect( jointName, part ) { this.children.push( part ); part.parent = this; //check. if( ! this.jointHash[ jointName ] ) { alert( "undefined key '" + jointName + "' in jointHash" ); } part.parentJoint = this.jointHash[ jointName ]; } //たとえば、足を地面に接地する等 adjust( adjustFrom, adjustFromJointName, adjustTo, adjustToJointName ) { this.adjustFrom = adjustFrom; this.adjustFromJointName = adjustFromJointName; this.adjustTo = adjustTo; this.adjustToJointName = adjustToJointName; } adjustByThisStop( adjustFrom, adjustFromJointName, adjustTo ) { //check. calc()が行われる前はabs.jointHashは未作成 if( ! adjustFrom.abs.jointHash ) { alert( ( function() {/* 最初の描画でadjustByThisStop()を行おうとしましたが、 adjustByThisStop()は「前回の描画をもとにして adjust()を行うメソッド」なので実行できません。 */} ).toString().match( /\/\*\s*([\s\S]*)\s*\*\// )[ 1 ].replace( /\t/g, "" ) ); return; } let joint = adjustFrom.abs.jointHash[ adjustFromJointName ]; adjustTo.addJoint( "this stop", joint.x, joint.y ); this.adjust( adjustFrom, adjustFromJointName, adjustTo, "this stop" ); } reset() { this.rotation = 0; this.testR = 0; //子 for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.reset(); } } calc() { //自身の値の絶対座標を求める。 if( ! this.parent ) { //親がない場合は this.abs.x = 0; this.abs.y = 0; this.abs.rotation = this.rotation; } else { //親がある場合は //check. if( ! this.parentJoint ) { alert( "undefined parentJoint at " + this.title ); } let parentJointAbs = this.parent.abs.jointHash[ this.parentJoint.name ]; this.abs.x = parentJointAbs.x; this.abs.y = parentJointAbs.y; this.abs.rotation = this.parent.abs.rotation + this.rotation; } //各jointの絶対座標を求める。 this.abs.jointHash = new Object(); for( let name in this.jointHash ) { let j = this.jointHash[ name ]; let res = this.mathRotate( j.x, j.y, this.abs.rotation ); this.abs.jointHash[ name ] = { x : this.abs.x + res.X, y : this.abs.y + res.Y, } } //子もcalc() for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.calc(); } //---接地 if( this.adjustFrom ) { let from = this.adjustFrom.abs.jointHash[ this.adjustFromJointName ]; let to = this.adjustTo.abs.jointHash[ this.adjustToJointName ]; let adjustX = to.x - from.x; let adjustY = to.y - from.y; let adjustRotation = this.adjustTo.abs.rotation - this.adjustFrom.abs.rotation + this.testR; let cx = to.x; let cy = to.y; this.calc2( adjustX, adjustY, adjustRotation, cx, cy ); } }//calc() calc2( adjustX, adjustY, adjustRotation, cx, cy ) { this.abs.x += adjustX; this.abs.y += adjustY; this.abs.rotation += adjustRotation; //adjust回転 let res = this.mathRotateC( this.abs.x, this.abs.y, cx, cy, adjustRotation ); this.abs.x = res.X; this.abs.y = res.Y; //各jointについてadjust回転 for( let name in this.abs.jointHash ) { let x = this.abs.jointHash[ name ].x + adjustX; let y = this.abs.jointHash[ name ].y + adjustY; let res = this.mathRotateC( x, y, cx, cy, adjustRotation ); this.abs.jointHash[ name ].x = res.X; this.abs.jointHash[ name ].y = res.Y; } //子について for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.calc2( adjustX, adjustY, adjustRotation, cx, cy ); } } //SVC_Part_20201026215429 draw( cc ) { //自身を描画 cc.save(); cc.translate( this.abs.x, this.abs.y ); cc.rotate( this.abs.rotation ); cc.translate( this.imgd.x, this.imgd.y ); this.imgd.draw( cc ); cc.restore(); //子を描画 for( let i = 0; i < this.children.length; i++ ) { let child = this.children[ i ]; child.draw( cc ); } //debug. if( this.debug1 ) { //各jointを○で示す for( let name in this.jointHash ) { let joint = this.abs.jointHash[ name ]; cc.save(); cc.translate( joint.x, joint.y ); cc.scale( 1, -1 ); cc.globalAlpha = .3; cc.beginPath(); cc.arc( 0, 0, 3, 0, 6.28, false ); cc.closePath(); cc.strokeStyle = "red"; cc.stroke(); cc.font = "6px''"; cc.fillText( name, 0, 0 ); cc.restore(); } } }//draw() //--- mathRotate( x, y, theta2 ) { let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei, Y : Math.sin( theta1 + theta2 ) * hankei, } } mathRotateC( x, y, cx, cy, theta2 ) { x -= cx; y -= cy; let theta1 = Math.atan2( y, x ); let hankei = Math.sqrt( x * x + y * y ); return { X : Math.cos( theta1 + theta2 ) * hankei + cx, Y : Math.sin( theta1 + theta2 ) * hankei + cy, } } }//SVC_Part_20201026215429 class TestRect_20201026215429 { constructor( type ) { this.x = 0; this.y = 0; this.w = 10; this.h = 10; this.visibility = true; this.type = type; } draw( cc ) { //check. if( ! this.visibility ) return; switch( this.type ) { case "rect": cc.strokeStyle = "black"; cc.strokeRect( 0, 0, this.w, this.h ); break; case "horizon": cc.fillStyle = "brown"; let w = 130; let h = 23; cc.fillRect( -w, -h, w * 2, h ); break; default: alert( "undefined type: '" + this.type + "'" ); } } } class Kiseki_20201026215429 { constructor( args ) { this.f = function( x ) { return x; }; this.x = 0; this.y = 0; this.zoomX = 1; this.zoomY = 1; this.sx = -10; this.ex = 10; this.step = 1; this.cursor = 0; Object.assign( this, args ); this.adjustX = this.sx * this.zoomX * this.zoom; this.adjustY = this.f( this.sx ) * this.zoomY * this.zoom; this.cursorMax = Math.abs( this.ex - this.sx ) / this.step; this.dir = this.ex >= this.sx ? 1 : -1; } cursorG() { return this.indexG( this.cursor ); } indexG( index ) { let x = this.sx + index * this.step * this.dir; //グラフ上のx return { //画面上のx,y graphX : x, x : this.x + x * this.zoomX * this.zoom - this.adjustX, y : this.y + this.f( x ) * this.zoomY * this.zoom - this.adjustY, }; } draw( cc ) { cc.circle( this.x, this.y, 4, null, "red" ); for( let i = 0; i <= this.cursorMax; i++ ) { let g = this.indexG( i ); //check. 原点 if( g.graphX == 0 ) { cc.line( g.x - 50, g.y, g.x + 50, g.y, "lightgray" ); cc.line( g.x, g.y - 50, g.x, g.y + 50, "lightgray" ); } cc.circle( g.x, g.y, 1, i == this.cursor ? "red" : "blue" ); } } }